package com.server.common.redis.token;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.NumberUtil;

import com.server.common.code.error.BusinessException;
import com.server.common.code.utils.OptionalUtils;
import com.server.common.code.vo.LoginVo;
import com.server.common.redis.RedisService;
import com.server.common.redis.constant.RedisKeysConstant;
import com.server.common.redis.vo.TokenVo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * 单点登录
 * @author LXC
 * @date 2022/4/29 13:49
 */
@Component
@Slf4j
public class TokenServices {

    @Resource
    private RedisService redisService;

    @Resource
    private RedisTemplate<String, String> redisTemplate;

    //登录失败次数
    private static final Long LOGIN_FAIL_NUM = 10L;

    //登录失败 不可再次登录的时间-3分钟
    private static final Long LOGIN_FAIL_TIME = 1000*60*3L;

    //间隔登录时间 10s
    private static final Long INTERVAL_LOGIN_TIME = 1000*10L;

    /**
     * 2万/6秒
     * @author LXC
     * @date 2022/5/16 10:24
     */
    public TokenVo createToken(LoginVo loginVo) {
        String token = UUID.randomUUID().toString();

        //获取当前时间
        long currentTime = DateUtil.current();

        //获取续费时间
        Date renewalTime = DateUtil.offsetMinute(DateUtil.date(),4);
        loginVo.setRenewalTime(renewalTime.getTime());

        //获取到期时间
        Date expireTime = DateUtil.offsetMinute(DateUtil.date(),8);
        loginVo.setExpireTime(expireTime.getTime());

        //获取当前时间
        loginVo.setLoginTime(currentTime);

        //获取token过期时间
        long redisExpireTime = expireTime.getTime() - currentTime;

        //redis管道
        redisTemplate.executePipelined((RedisCallback<Object>) connection -> {
            //校验登录时间频繁
            checkLastLoginTime(loginVo.getUserId(),currentTime);

            //校验登录次数--强退，先进先强退
            checkLoginTokenList(loginVo.getUserId(),loginVo.getOnlineNum());

            //存入Redis
            setToken(token,loginVo,redisExpireTime,TimeUnit.MILLISECONDS);

            //记录最后一次登录时间
            lastLoginTimeRefresh(loginVo.getUserId(),currentTime);

            //设置token集合过期时间
            setLoginTokenListExpire(loginVo.getUserId(),token,redisExpireTime,TimeUnit.MILLISECONDS);
            return null;
        });

        TokenVo tokenVo = new TokenVo();
        tokenVo.setRenewalTime(renewalTime.getTime());
        tokenVo.setExpireTime(expireTime.getTime());
        tokenVo.setToken(token);
        return tokenVo;
    }

    /**
     * 2万/6秒
     * @author LXC
     * @date 2022/5/16 10:24
     */
    public TokenVo refreshToken(String tokenOld,LoginVo loginVo) {
        deleteToken(tokenOld);
        return createToken(loginVo);
    }


    /**
     * 获取token
     * @author LXC
     * @date 2022/11/5 19:35
     */
    public LoginVo getToken(String token){
        return redisService.getValue(RedisKeysConstant.TOKEN + token,LoginVo.class);
    }

    /**
     * 删除token
     * @author LXC
     * @date 2022/11/5 19:35
     */
    public Boolean deleteToken(String token){
        return redisTemplate.delete(RedisKeysConstant.TOKEN + token);
    }

    /**
     * 设置token
     * @author LXC
     * @date 2022/11/5 19:35
     */
    public void setToken(String token,LoginVo loginVo,Long redisExpireTime,TimeUnit unit){
        redisService.setValue(RedisKeysConstant.TOKEN + token,loginVo,redisExpireTime,unit);
    }

    /**
     * IP 登录失败次数加1
     * @author LXC
     * @date 2022/11/7 18:52
     */
    public void loginFailNumIncrement(String ip){
        String key = RedisKeysConstant.LOGIN_FAIL_NUM + ip;
        redisTemplate.opsForValue().increment(key);
        redisTemplate.expire(key,LOGIN_FAIL_TIME,TimeUnit.MILLISECONDS);
    }

    /**
     * 判断登录失败次数过多
     * @author LXC
     * @date 2022/11/7 23:29
     */
    public void checkLoginFailNumMore(String ip){
        String key = RedisKeysConstant.LOGIN_FAIL_NUM + ip;
        Integer value = NumberUtil.parseInt(redisService.getValue(key));

        OptionalUtils.ofEmpty(value).orElseThrow(()->new BusinessException("登录频繁,稍后再试!"));

        if(value > LOGIN_FAIL_NUM){
            throw new BusinessException("登录频繁,稍后再试!");
        }
    }

    /**
     * 登录失败次数删除
     * @author LXC
     * @date 2022/11/8 22:44
     */
    public void loginFailNumDel(String ip){
        String key = RedisKeysConstant.LOGIN_FAIL_NUM + ip;
        redisTemplate.delete(key);
    }

    /**
     * 判断最后一次登录时间
     * @author LXC
     * @date 2022/11/8 23:12
     */
    public void checkLastLoginTime(Long userId,Long currentTime){
        String lastLoginTimeKey = RedisKeysConstant.LAST_LOGIN_TIME + userId;
        //获取最后一次登录时间
        long lastLoginTime = NumberUtil.parseLong(redisService.getValue(lastLoginTimeKey));

        //间隔登录时间
        if(currentTime - lastLoginTime <= INTERVAL_LOGIN_TIME){
            throw new BusinessException("登录频繁,请稍后再试!");
        }
    }

    /**
     * 登录时间刷新
     * @author LXC
     * @date 2022/11/8 23:14
     */
    public void lastLoginTimeRefresh(Long userId,Long currentTime){
        String lastLoginTimeKey = RedisKeysConstant.LAST_LOGIN_TIME + userId;
        redisService.setValue(lastLoginTimeKey,currentTime,INTERVAL_LOGIN_TIME,TimeUnit.MILLISECONDS);
    }

    /**
     * 校验登录次数
     * @author LXC
     * @date 2022/11/8 23:20
     */
    public  void checkLoginTokenList(Long userId, Long onlineNum){
        if(0 == onlineNum){
            throw new BusinessException("不可登录!");
        }

        String tokenListKey = RedisKeysConstant.TOKEN_LIST + userId;
        Set<String> tokensSet = redisTemplate.opsForSet().members(tokenListKey);

        //登录上限--移除一个
        Optional.ofNullable(tokensSet)
                .orElse(new HashSet<>())
                .stream()
                .filter(t -> onlineNum == tokensSet.size())
                .findFirst()
                .ifPresent(firstToken->{
                    deleteToken(firstToken);
                    tokensSet.remove(firstToken);
                    redisTemplate.opsForSet().remove(tokenListKey,firstToken);
                });

        //判断Token是否过期，如果过期则移除，没有过期则跳过
        Optional.ofNullable(tokensSet)
                .orElse(new HashSet<>())
                .stream()
                .filter(t->redisTemplate.hasKey(t))
                .forEach(t->{
                    redisTemplate.opsForSet().remove(tokenListKey,t);
                });

    }

    /**
     * 设置token集合过期时间
     * @author LXC
     * @date 2022/11/8 23:22
     */
    public void setLoginTokenListExpire(Long userId, String token, Long redisExpireTime, TimeUnit unit){
        String tokenListKey = RedisKeysConstant.TOKEN_LIST + userId;
        //登录过的Token存入
        redisTemplate.opsForSet().add(tokenListKey,token);

        //设置过期时间
        redisTemplate.expire(tokenListKey,redisExpireTime,unit);
    }

}
